package com.bzkj.framework.aspectj;

import com.alibaba.fastjson2.JSON;
import com.alibaba.fastjson2.JSONObject;
import com.bzkj.common.annotation.LogB;
import com.bzkj.common.core.domain.AjaxResult;
import com.bzkj.common.core.domain.model.LoginUser;
import com.bzkj.common.enums.BusinessStatus;
import com.bzkj.common.enums.BusinessType;
import com.bzkj.common.enums.HttpMethod;
import com.bzkj.common.filter.PropertyPreExcludeFilter;
import com.bzkj.common.utils.SecurityUtils;
import com.bzkj.common.utils.ServletUtils;
import com.bzkj.common.utils.StringUtils;
import com.bzkj.common.utils.ip.AddressUtils;
import com.bzkj.common.utils.ip.IpUtils;
import com.bzkj.framework.manager.AsyncManager;
import com.bzkj.framework.manager.factory.AsyncFactory;
import com.bzkj.quality.domain.QmsCheckDTO;
import com.bzkj.system.domain.SysOperOrderLog;
import com.bzkj.system.service.ISysOperOrderLogService;
import org.apache.commons.lang3.ArrayUtils;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.core.NamedThreadLocal;
import org.springframework.stereotype.Component;
import org.springframework.validation.BindingResult;
import org.springframework.web.multipart.MultipartFile;

import javax.annotation.Resource;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.text.MessageFormat;
import java.util.*;
import java.util.stream.Collectors;

/**
 * 操作日志记录处理
 *
 * @author ruoyi
 */
@Aspect
@Component
public class LogAspectB {
    private static final Logger log = LoggerFactory.getLogger(LogAspectB.class);

    // 使用更规范的日志格式
    private static final String LOG_FORMAT = "{0} {1} {2},单据号：{3}。";

    @Resource
    private ISysOperOrderLogService operLogService;

    /**
     * 排除敏感属性字段
     */
    public static final String[] EXCLUDE_PROPERTIES = {"password", "oldPassword", "newPassword", "confirmPassword"};

    /**
     * 计算操作消耗时间
     */
    private static final ThreadLocal<Long> TIME_THREADLOCAL = new NamedThreadLocal<Long>("Cost Time");

    @Around(value = "@annotation(logB)", argNames = "pjp,logB")
    public Object handleLog(ProceedingJoinPoint pjp, LogB logB) throws Throwable {
        TIME_THREADLOCAL.set(System.currentTimeMillis());
        Object result = null;
        SysOperOrderLog operLog = null;
        try {
            LoginUser loginUser = SecurityUtils.getLoginUser();
            operLog = createOperLog(pjp, loginUser, logB);
            setMethodInfo(pjp, operLog);
            result = getControllerMethodDescription(pjp, logB, operLog);
        } catch (Exception exp) {
            log.error("操作日志记录异常 - method: {}, error: {}",
                    pjp.getSignature().getName(), exp.getMessage(), exp);
            if (operLog != null) {
                handleException(operLog, exp);
            }
            throw exp;
        } finally {
            TIME_THREADLOCAL.remove();
        }
        return result;
    }

    // 新增方法处理异常情况
    private void handleException(SysOperOrderLog operLog, Exception exp) {
        operLog.setStatus(BusinessStatus.FAIL.ordinal());
        operLog.setErrorMsg(StringUtils.substring(exp.getMessage(), 0, 2000));
        AsyncManager.me().execute(AsyncFactory.recordOrderOper(operLog));
        // 添加更详细的异常日志记录
        log.error("操作日志记录异常 - 操作人: {}, 操作标题: {}, 错误信息: {}",
                operLog.getOperName(),
                operLog.getTitle(),
                exp.getMessage(),
                exp);
    }

    /**
     * 创建操作日志对象并设置基本信息
     */
    private SysOperOrderLog createOperLog(ProceedingJoinPoint joinPoint, LoginUser loginUser, LogB logB) {
        SysOperOrderLog operLog = new SysOperOrderLog();
        operLog.setStatus(BusinessStatus.SUCCESS.ordinal());

        // 请求的地址--ip
        String ip = IpUtils.getIpAddr(ServletUtils.getRequest());
        operLog.setOperIp(ip);

        // 请求的地址--url
        operLog.setOperUrl(StringUtils.substring(ServletUtils.getRequest().getRequestURI(), 0, 255));

        // 操作人
        if (loginUser != null) {
            operLog.setOperName(loginUser.getUsername());
        }
        operLog.setOperLocation(AddressUtils.getRealAddressByIP(ip));

        // 设置action动作
        operLog.setBusinessType(logB.businessType().ordinal());
        // 设置标题
        operLog.setTitle(logB.title());
        // 设置操作人类别
        operLog.setOperatorType(logB.operatorType().ordinal());

        return operLog;
    }

    /**
     * 设置方法信息
     */
    private void setMethodInfo(ProceedingJoinPoint joinPoint, SysOperOrderLog operLog) {
        String className = joinPoint.getTarget().getClass().getName();
        String methodName = joinPoint.getSignature().getName();
        operLog.setMethod(className + "." + methodName + "()");

        // 设置请求方式
        operLog.setRequestMethod(ServletUtils.getRequest().getMethod());
    }

    /**
     * 获取注解中对方法的描述信息 用于Controller层注解
     */
    public Object getControllerMethodDescription(ProceedingJoinPoint joinPoint, LogB log, SysOperOrderLog operLog) throws Throwable {
        Object result;
        if (log.businessType().ordinal() == BusinessType.UPDATE.ordinal()) {
            result = processUpdateLog(joinPoint, log, operLog);
        } else {
            result = joinPoint.proceed();
            handleNonUpdateLog(joinPoint, log, operLog, result);
        }
        return result;
    }

    // 新增方法处理非UPDATE类型的日志
    private void handleNonUpdateLog(ProceedingJoinPoint joinPoint, LogB log, SysOperOrderLog operLog, Object result) {
        AjaxResult ajaxResult = (AjaxResult) result;
        if (ajaxResult.isSuccess()) {
            String vbillcode = extractVbillcode(ajaxResult);
            if (log.businessType().ordinal() == BusinessType.CHECK.ordinal()) {
                handleCheckLog(joinPoint, operLog, vbillcode);
            } else {
                operLog.setOperDesc(MessageFormat.format(LOG_FORMAT,
                        StringUtils.defaultIfEmpty(operLog.getOperName(), ""),
                        log.businessType().getDescription(),
                        StringUtils.defaultIfEmpty(operLog.getTitle(), ""),
                        StringUtils.defaultIfEmpty(vbillcode, "")));
            }
        } else {
            operLog.setStatus(BusinessStatus.FAIL.ordinal());
            operLog.setErrorMsg(ajaxResult.get("msg").toString().replaceAll("</?[a-zA-Z]+>", ""));
        }
        operLog.setCostTime(System.currentTimeMillis() - TIME_THREADLOCAL.get());
        AsyncManager.me().execute(AsyncFactory.recordOrderOper(operLog));
    }

    // 新增方法提取单据号
    private String extractVbillcode(AjaxResult ajaxResult) {
        String msg = ajaxResult.get("msg").toString();
//        String[] split = msg.split("【");
//        String[] split1 = split[1].split("】");
        return msg;
    }

    // 新增方法处理检查类型的日志
    private void handleCheckLog(ProceedingJoinPoint joinPoint, SysOperOrderLog operLog, String vbillcode) {
        Object[] args = joinPoint.getArgs();
        for (Object arg : args) {
            JSONObject jsonObject = new JSONObject();
            jsonObject.put(arg.getClass().getName(), arg);
            JSONObject jsonObject1 = jsonObject.getJSONObject(arg.getClass().getName());
            if (jsonObject1.containsKey("detailsData")) {
                QmsCheckDTO qmsCheckDTO = jsonObject1.getJSONObject("detailsData").toJavaObject(QmsCheckDTO.class);
                String action = qmsCheckDTO.getModeType() == 1 ? "复验" : "请验";
                operLog.setOperDesc(MessageFormat.format(LOG_FORMAT,
                        StringUtils.defaultIfEmpty(operLog.getOperName(), ""),
                        StringUtils.defaultIfEmpty("在"+operLog.getTitle()+"中发起了", ""),
                        action,
                        StringUtils.defaultIfEmpty(vbillcode, "")));
                break;
            }
        }
    }

    /**
     * 处理请求
     *
     * @param joinPoint 切点
     * @param log       日志注解
     * @param operLog   操作日志
     */
    public Object processUpdateLog(ProceedingJoinPoint joinPoint, LogB log, SysOperOrderLog operLog) throws Throwable {
        // 提取主要所在的表
        String tableName = log.tableNames();
        // 提取主要表主键
        String pkFieldNames = log.pkFieldNames();
        // 提取关联表
        String detailTable = log.detailTable();
        // 提取表头表和关联表的关联键
        String detailIdField = log.detailIdField();
        // 提取明细表主键
        String detailPkField = log.detailPkField();
        // 获取方法参数，解析主键 ID
        Object[] args = joinPoint.getArgs();
        Object primaryKey = extractPrimaryKey(pkFieldNames, args);

        // 参数校验：如果关键参数为空，则跳过日志记录，直接执行方法
        if (StringUtils.isEmpty(tableName) || StringUtils.isEmpty(pkFieldNames) || primaryKey == null) {
            return joinPoint.proceed();
        }

        // 获取旧数据
        long sqlStart = System.currentTimeMillis();
        Map<String, Object> oldHeader = operLogService.selectOrderHeaderByPk(pkFieldNames, tableName, primaryKey);
        List<Map<String, Object>> oldDetails = detailTable.isEmpty() ? null : operLogService.selectOrderDetailsByPk(detailIdField, detailTable, primaryKey);
        long sqlEnd = System.currentTimeMillis();

        // 执行原方法（更新数据库）
        Object result = joinPoint.proceed();
        AjaxResult ajaxResult = (AjaxResult) result;
        if (ajaxResult.isSuccess()) {
            // 计算耗时
            operLog.setCostTime(System.currentTimeMillis() - TIME_THREADLOCAL.get() - (sqlEnd - sqlStart));

            // 查询新数据
            Map<String, Object> newHeader = operLogService.selectOrderHeaderByPk(pkFieldNames, tableName, primaryKey);
            List<Map<String, Object>> newDetails = detailTable.isEmpty() ? null : operLogService.selectOrderDetailsByPk(detailIdField, detailTable, primaryKey);

            // 获取字段翻译集
            Map<String, String> fieldTranslationMap = operLogService.getFieldTranslations(tableName)
                    .stream()
                    .collect(Collectors.toMap(m -> m.get("field_name"), m -> m.get("field_cn_name")));

            // 比较并翻译字段
            Map<String, Map<String, String>> translateChanges = translateChanges(fieldTranslationMap, oldHeader, newHeader);
            operLog.setOperDesc(operLog.getOperName() + log.businessType().getDescription() + operLog.getTitle() + ",单据号：" + oldHeader.get("vbillcode") + "。");
            operLog.setDetailType("header");
            operLog.setOperationDetail(JSONObject.toJSONString(translateChanges).replaceAll("</?[a-zA-Z]+>", ""));
            // 存储订单头操作记录
            Long parentId = operLogService.insertOperlog(operLog);
            assert oldDetails != null;
            for (Map<String, Object> oldDetail : oldDetails) {
                // 获取字段翻译集
                Map<String, String> fieldTranslationMapA = operLogService.getFieldTranslations(detailTable)
                        .stream()
                        .collect(Collectors.toMap(m -> m.get("field_name"), m -> m.get("field_cn_name")));
                assert newDetails != null;

                newDetails.forEach(newDetail -> {
                    if (newDetail.get(detailPkField).equals(oldDetail.get(detailPkField))) {
                        operLog.setDetailType("detail");
                        operLog.setParentId(parentId);
                        Map<String, Map<String, String>> detailChangeMap = translateChanges(fieldTranslationMapA, oldDetail, newDetail);
                        HashMap<String, Object> detailMap = new HashMap<>(1);
                        detailMap.put("行号：" + newDetail.get("crowno"), detailChangeMap);
                        operLog.setOperationDetail(JSONObject.toJSONString(detailMap).replaceAll("</?[a-zA-Z]+>", ""));
                        operLogService.insertOperlog(operLog);
                    }
                });
            }
        } else {
            operLog.setStatus(BusinessStatus.FAIL.ordinal());
            operLog.setErrorMsg(ajaxResult.get("msg").toString().replaceAll("</?[a-zA-Z]+>", ""));
            operLogService.insertOperlog(operLog);
        }

        return result;
    }

    private Object extractPrimaryKey(String idField, Object[] args) {
        for (Object arg : args) {
            if (arg instanceof Map) {
                Map<?, ?> map = (Map<?, ?>) arg;
                Map<?, ?> header = (Map<?, ?>) map.get("header");
                if (header == null) {
                    header = (Map<?, ?>) map.get("ic_saleout_h");
                }
                if (header == null) {
                    header = (Map<?, ?>) map.get("ic_finprodin_h");
                }
                if (header == null) {
                    header = (Map<?, ?>) map.get("ic_generalin_h");
                }
                if (header == null) {
                    header = (Map<?, ?>) map.get("ic_material_h");
                }
                if (header == null) {
                    header = (Map<?, ?>) map.get("ic_generalout_h");
                }
                if (header == null) {
                    header = (Map<?, ?>) map.get("ic_salein_h");
                }
                if (header == null) {
                    header = (Map<?, ?>) map.get("ic_whstrans_h");
                }
                if (header != null) {
                    return header.get(idField);
                }
            }
        }
        return null;
    }

    /**
     * 生成修改日志，翻译字段
     */
    private Map<String, Map<String, String>> translateChanges(Map<String, String> fieldTranslationMap,
                                                              Map<String, Object> oldData, Map<String, Object> newData) {
        Map<String, Map<String, String>> changes = new HashMap<>();
        for (String key : newData.keySet()) {
            if (oldData != null && oldData.containsKey(key) && !Objects.equals(oldData.get(key), newData.get(key))) {
                String fieldDesc = fieldTranslationMap.get(key);
                if (fieldDesc != null) {
                    HashMap<String, String> detailMap = new HashMap<>();
                    detailMap.put("oldValue", oldData.get(key).toString());
                    detailMap.put("newValue", newData.get(key).toString());
                    changes.put(fieldDesc, detailMap);
                }
            }
        }
        return changes;
    }


    /**
     * 获取请求的参数，放到log中
     *
     * @param operLog 操作日志
     * @throws Exception 异常
     */
    private void setRequestValue(JoinPoint joinPoint, SysOperOrderLog operLog, String[] excludeParamNames) throws Exception {
        Map<?, ?> paramsMap = ServletUtils.getParamMap(ServletUtils.getRequest());
        String requestMethod = operLog.getRequestMethod();
        if (StringUtils.isEmpty(paramsMap)
                && (HttpMethod.PUT.name().equals(requestMethod) || HttpMethod.POST.name().equals(requestMethod))) {
            String params = argsArrayToString(joinPoint.getArgs(), excludeParamNames);
            operLog.setOperParam(StringUtils.substring(params, 0, 2000));
        } else {
            operLog.setOperParam(StringUtils.substring(JSON.toJSONString(paramsMap, excludePropertyPreFilter(excludeParamNames)), 0, 2000));
        }
    }

    /**
     * 参数拼装
     */
    private String argsArrayToString(Object[] paramsArray, String[] excludeParamNames) {
        StringBuilder params = new StringBuilder();
        if (paramsArray != null) {
            for (Object o : paramsArray) {
                if (StringUtils.isNotNull(o) && !isFilterObject(o)) {
                    try {
                        String jsonObj = JSON.toJSONString(o, excludePropertyPreFilter(excludeParamNames));
                        params.append(jsonObj).append(" ");
                    } catch (Exception e) {
                        throw new RuntimeException(e);
                    }
                }
            }
        }
        return params.toString().trim();
    }

    /**
     * 忽略敏感属性
     */
    public PropertyPreExcludeFilter excludePropertyPreFilter(String[] excludeParamNames) {
        return new PropertyPreExcludeFilter().addExcludes(ArrayUtils.addAll(EXCLUDE_PROPERTIES, excludeParamNames));
    }

    /**
     * 判断是否需要过滤的对象。
     *
     * @param o 对象信息。
     * @return 如果是需要过滤的对象，则返回true；否则返回false。
     */
    @SuppressWarnings("rawtypes")
    public boolean isFilterObject(final Object o) {
        Class<?> clazz = o.getClass();
        if (clazz.isArray()) {
            return clazz.getComponentType().isAssignableFrom(MultipartFile.class);
        } else if (Collection.class.isAssignableFrom(clazz)) {
            Collection collection = (Collection) o;
            for (Object value : collection) {
                return value instanceof MultipartFile;
            }
        } else if (Map.class.isAssignableFrom(clazz)) {
            Map map = (Map) o;
            for (Object value : map.entrySet()) {
                Map.Entry entry = (Map.Entry) value;
                return entry.getValue() instanceof MultipartFile;
            }
        }
        return o instanceof MultipartFile || o instanceof HttpServletRequest || o instanceof HttpServletResponse
                || o instanceof BindingResult;
    }
}
